


var RAND = function(MIN,MAX){
    var dt = MAX - MIN;
    var r = cc.RANDOM_0_1() * dt;
    return (MIN + r);
};


var MAX_ParticlS = 500;
var MAX_MESH = 50;
var MAX_RIGID_BODY = 50;
var MAX_OBJECT = 50;


var g_Particls = [];
var g_Meshs = [];
var g_Bodies = [];

var g_PhysicSystem = new _PhysicSystem();



function createParticl(){
    var p = new _Particl();
    g_Particls.push(p);
    return (g_Particls.length - 1);
};
function _Particl () {
    this._initialPosition = cc.p(0.0, 0.0);
    this._relativePosition = cc.p(0.0, 0.0);
    this._worldPosition = cc.p(0.0, 0.0);
    this._linearVelocity = cc.p(0.0, 0.0);
    this._force = cc.p(0.0, 0.0);
    this._radius = 5.0;
}

function createParticlMesh(){
    var mesh = new _ParticlMesh();
    g_Meshs.push( mesh );
    return (g_Meshs.length - 1);
};
function _ParticlMesh () {
    this._ParticlsIdx = [];
}

function createRigidBody(){
    var rb = new _rigidBody();
    g_Bodies.push(rb);
    return (g_Bodies.length - 1);
};

var TypeEnum = {
    Dynamic : 0,
    Static : 1,
    Paticle : 2
};

function _rigidBody () {
    this._Type = TypeEnum.Dynamic;
    this._position = cc.p(0.0, 0.0);
    this._angle = 0;
    this._linearVelocity = cc.p(0.0, 0.0);
    this._angularVelocity = 0.0;
    this._force = cc.p(0.0, 0.0);
    this._angularMoment = 0;
    this._mass = 1.0;
    this._invMass = 1.0;
    this._I = 1.0;
    this._invI = 1.0;
    this._meshIdx = -1;
}






function _PhysicSystem () {

    // Setp 0 : update
    this.update = function(dt){
        this.computeParticlsValues(dt);
        this.computeCollisionReaction(dt);
        this.computeMomenta(dt);
        this.computeBodiesPoisitionAngle(dt);
        
        // need to display Particl at correct position, otherwise Particl have one frame late from rigidBodies.
        // !!! if not need to display, remove this call for faster update function !!!
        this.computeParticlsValues(dt);
    };
    
    // Step 1: Computation of Particl Values
    this.computeParticlsValues = function(dt){
        var bodies_number = g_Bodies.length;
        for(var i=0; i < bodies_number; i++){
            this.computeParticlsOfBody(i,dt);
        }
    };
    this.computeParticlsOfBody = function(bodyIdx,dt){
        var b = g_Bodies[ bodyIdx ];
        if(b._meshIdx != -1){
            var m = g_Meshs[ b._meshIdx ];
            var Particls_number = m._ParticlsIdx.length;
            for(var i=0; i < Particls_number; i++){
                this.computeParticlOfBody( m._ParticlsIdx[i], bodyIdx, dt );
            }
        }
    };
    this.computeParticlOfBody = function(ParticlIdx,bodyIdx,dt){
        var b = g_Bodies[ bodyIdx ];
        var angle = b._angle;
        var position = b._position;
        var linearVelocity = b._linearVelocity;
        var angularVelocity = b._angularVelocity;
        
        var p = g_Particls[ ParticlIdx ];
        var r0 = p._initialPosition;
        var ri;
        var w;
        var v;
        
        var rMat = [ Math.cos(angle), -Math.sin(angle),
                     Math.sin(angle), Math.cos(angle) ]; 
        ri  = cc.p( (r0.x * rMat[0]) + (r0.y * rMat[1]),
                    (r0.x * rMat[2]) + (r0.y * rMat[3]) ); 
        w    = cc.p( position.x + ri.x, 
                    position.y + ri.y );
        v     = cc.p( linearVelocity.x + (angularVelocity * ri.x ), 
                    linearVelocity.y + (angularVelocity * ri.y ) );
        
        
        p._relativePosition = ri;
        p._worldPosition = w;
        p._linearVelocity = v;
        p._force = cc.p(0.0, 0.0);
    }
    
    // /!\ not use yet /!\   Step 2: Grid Generation
    
    // Step 3: Collision Detection and Reaction
    this.computeCollisionReaction = function(dt){
        var Particls_number = g_Particls.length;
        for(var i=0; i < Particls_number; i++){
            this.computeCollisionReactionForPaticle( i, dt );
        }
    };
    this.computeCollisionReactionForPaticle = function(ParticlIdx,dt){
        var Particls_number = g_Particls.length;
        var p1 = g_Particls[ ParticlIdx ];
        var p2 = null;
        for(var i=0; i < Particls_number; i++){
            if(i == ParticlIdx){continue;}
            p2 = g_Particls[ i ];
            
            var R = p1._radius + p2._radius;
            var V = cc.p( p1._worldPosition.x - p2._worldPosition.x, 
                          p1._worldPosition.y - p2._worldPosition.y );
            var D = Math.sqrt(V.x * V.x + V.y * V.y);
            
            if( D < R ){
                var Vn = cc.p( V.x/D , V.y/D );
                var power = (R-D)*2.0;
                var force = cc.p( Vn.x*power , Vn.y*power );
                p1._force = cc.p( p1._force.x + force.x, p1._force.y + force.y ); 
                
                p1._force = cc.p( p1._force.x - p1._linearVelocity.x, p1._force.y - p1._linearVelocity.y ); 
            }
        }
    };
    
    // Step 4: Computation of Momenta
    this.computeMomenta = function(dt){
        var bodies_number = g_Bodies.length;
        for(var i=0; i < bodies_number; i++){
            this.computeMomentaOfBody(i,dt);
        }
    };
    this.computeMomentaOfBody = function(bodyIdx,dt){
        var b = g_Bodies[ bodyIdx ];
        if( b._Type == TypeEnum.Dynamic && b._meshIdx != -1){
            var m = g_Meshs[ b._meshIdx ];
            var Particls_number = m._ParticlsIdx.length;
            for(var i=0; i < Particls_number; i++){
                var p = g_Particls[ m._ParticlsIdx[i] ];
                this.AddRelativeImpulse( bodyIdx, p._force, p._relativePosition );
                p._force = cc.p(0.0, 0.0);
            }
        }
    };
    
    // Step 5: Computation of Position and Angle
    this.computeBodiesPoisitionAngle = function(dt){
        var bodies_number = g_Bodies.length;
        for(var i=0; i < bodies_number; i++){
            this.computeBodyPoisitionAngle(i,dt);
        }
    };
    this.computeBodyPoisitionAngle = function(bodyIdx,dt){
        var b = g_Bodies[ bodyIdx ];
        if( b._Type == TypeEnum.Dynamic ){
            b._linearVelocity.x += dt * (b._force.x * b._invMass);
            b._linearVelocity.y += dt * (b._force.y * b._invMass);
            b._angularVelocity  += dt * (b._angularMoment * b._invI); 

            b._position.x += dt * b._linearVelocity.x;
            b._position.y += dt * b._linearVelocity.y;
            b._angle += dt * b._angularVelocity;
        }
    };
    
    // Other Function
    this.computeInertia = function(bodyIdx){
        var b = g_Bodies[ bodyIdx ];
        var I = 0;
        if(b._meshIdx != -1){
            var m = g_Meshs[ b._meshIdx ];
            var Particls_number = m._ParticlsIdx.length;
            var massP = b._mass / Particls_number;
            for(var i=0; i < Particls_number; i++){
                var p = g_Particls[ m._ParticlsIdx[i] ];
                I += massP * Math.sqrt( (p._initialPosition.x * p._initialPosition.x) + (p._initialPosition.y * p._initialPosition.y) );
            }
        }
        b._I = I + 0.0001;
        
        b._invMass = 1 / b._mass;
        b._invI = 1 / b._I;
    };
    
    this.AddForce = function(bodyIdx,force,position){
        var b = g_Bodies[ bodyIdx ];
        if(position == null){position = b._position;}
        
        this.AddRelativeForce(bodyIdx,force, cc.p(     (position.x - b._position.x),
                                                    (position.y - b._position.y) ) );
    };
    
    this.AddRelativeForce = function(bodyIdx,force,position){
        var b = g_Bodies[ bodyIdx ];
        if(position == null){position = cc.p(0,0);}
        
        b._force.x += force.x;
        b._force.y += force.y;
        b._angularMoment += ((position.x) * force.y - (position.y) * force.x);
    };
    
    this.AddImpulse = function(bodyIdx,impulse,position){
        var b = g_Bodies[ bodyIdx ];
        if(position == null){position = b._position;}
        
        this.AddRelativeImpulse(bodyIdx,impulse, cc.p(     (position.x - b._position.x),
                                                        (position.y - b._position.y) ) );
    };
    
    this.AddRelativeImpulse = function(bodyIdx,impulse,position){
        var b = g_Bodies[ bodyIdx ];
        if(position == null){position = cc.p(0,0);}
        
        b._linearVelocity.x += (impulse.x * b._invMass);
        b._linearVelocity.y += (impulse.y * b._invMass);
        b._angularVelocity +=  ((position.x) * impulse.y - 
                                (position.y) * impulse.x ) * b._invI;
    };
}


function _LimitToScreenSystem(){
    var bodies_number = g_Bodies.length;
    for(var i=0; i < bodies_number; i++){
        var b = g_Bodies[ i ];
        b._position.x = b._position.x > 405 ? -405 : b._position.x < -405 ? 405 : b._position.x;
        b._position.y = b._position.y > 230 ? -230 : b._position.y < -230 ? 230 : b._position.y;
    }
};

function _DrawAsCircleSystem(){
    var r = new cc.Color3B(255,0,0);
    var g = new cc.Color3B(0,255,0);
    var b = new cc.Color3B(0,0,255);
    
    var bodies_number = g_Bodies.length;
    for(var i=0; i < bodies_number; i++){
        var b = g_Bodies[ i ];
        cc.drawingUtil.drawCircle(b._position, 5, b._angle, 20, true, false, '#000000');
        
        var color = '#008000';
        if(b._Type == TypeEnum.Static ){
            color = '#800000';
        }
        
        if(b._meshIdx != -1){
            var m = g_Meshs[ b._meshIdx ];
            var Particls_number = m._ParticlsIdx.length;
            for(var j=0; j < Particls_number; j++){
                var p = g_Particls[ m._ParticlsIdx[j] ];
                cc.drawingUtil.drawCircle(p._worldPosition, p._radius, 0, 20, false, true, color);
            }
        }
    }
    
    
};

var _RigidBodyScene = cc.Layer.extend({
   
    
    init:function () {
        var selfPointer = this;
        //////////////////////////////
        // 1. super init first
        this._super();

        this.setTouchEnabled(true);
        this.setKeyboardEnabled(true);
        
        
        var size = cc.Director.getInstance().getWinSize();
        
        
        // background

        var background = cc.Sprite.create("res/back_waterScene.png");
        background.setPosition(cc.p(size.width / 2, size.height / 2));
        background.setVisible(true);
        background.setAnchorPoint(cc.p(0.5, 0.5));
        this.addChild(background, 0);
        
    
        //Menu (back button)
        var items = new Array();
        var p = cc.p( (-size.width/2) + 20 ,
                      (-size.height/2) + 30 );
        items[0] = createButton("Back");
        items[0].setPosition( p );
        items[0].setCallback(this,
            function () {
                myApp.GoToScene( new HelloWorldScene() );
            });
        var myMenu = cc.MenuCustom.create(items);
        this.addChild(myMenu);
        
        // update
        this.scheduleUpdate();
        
        // Particls
        var NumberOfRigidBodies = 40;
        for(var i=0; i<NumberOfRigidBodies; i++){
            var idx = createRigidBody();
            var b = g_Bodies[idx];
            b._mass = 1;
            b._position = cc.p( RAND(-400,400),RAND(-200,200) );
            g_PhysicSystem.AddImpulse(idx, cc.p( RAND(-80,80),RAND(-40,40) ), b._position );
            
            b._meshIdx = createParticlMesh();
            var m = g_Meshs[ b._meshIdx ];
            
            m._ParticlsIdx[0] = createParticl();
            var p = g_Particls[ m._ParticlsIdx[0] ];
            p._initialPosition = cc.p( 0,0 );
        }
        
        // Square (4 partciles)
        var NumberOfRigidBodies2 = 10;
        for(var i=0; i<NumberOfRigidBodies2; i++){
            var idx = createRigidBody();
            var b = g_Bodies[idx];
            b._mass = 1;
            b._position = cc.p( RAND(-400,400),RAND(-200,200) );
            g_PhysicSystem.AddImpulse(idx, cc.p( RAND(-80,80),RAND(-40,40) ), b._position );
            
            b._meshIdx = createParticlMesh();
            var m = g_Meshs[ b._meshIdx ];
            
            m._ParticlsIdx.push( createParticl() );
            var p = g_Particls[ m._ParticlsIdx[ m._ParticlsIdx.length - 1] ];
            p._initialPosition = cc.p( 5.1,5.1 );
            
            m._ParticlsIdx.push( createParticl() );
            p = g_Particls[ m._ParticlsIdx[ m._ParticlsIdx.length - 1] ];
            p._initialPosition = cc.p( 5.1,-5.1 );
            
            m._ParticlsIdx.push( createParticl() );
            p = g_Particls[ m._ParticlsIdx[ m._ParticlsIdx.length - 1] ];
            p._initialPosition = cc.p( -5.1,5.1 );
            
            m._ParticlsIdx.push( createParticl() );
            p = g_Particls[ m._ParticlsIdx[ m._ParticlsIdx.length - 1] ];
            p._initialPosition = cc.p( -5.1,-5.1 );
        }
        
        // Fix border (ligne of Particls)
        var NumberOfRigidBodies3 = 1;
        for(var i=0; i<NumberOfRigidBodies3; i++){
            var idx = createRigidBody();
            var b = g_Bodies[idx];
            b._Type = TypeEnum.Static;
            b._position = cc.p( -400 , -225 );
            b._mass = 1;
            
            
            b._meshIdx = createParticlMesh();
            var m = g_Meshs[ b._meshIdx ];
            
            for(var j=0; j<81; j++){
                m._ParticlsIdx.push( createParticl() );
                var p = g_Particls[ m._ParticlsIdx[ m._ParticlsIdx.length - 1] ];
                p._initialPosition = cc.p( 10 * j, 0 );
            }
            
            for(var j=0; j<81; j++){
                m._ParticlsIdx.push( createParticl() );
                var p = g_Particls[ m._ParticlsIdx[ m._ParticlsIdx.length - 1] ];
                p._initialPosition = cc.p( 10 * j, 10 );
            }
        }
        
        // compute inertia
        var bodies_number = g_Bodies.length;
        for(var i=0; i < bodies_number; i++){
            g_PhysicSystem.computeInertia(i);
            // apply gravity
            g_PhysicSystem.AddRelativeForce(i, cc.p( 0.0,-9.81 ) );
        }
        
        
        return true;
    },

    
    update:function (dt) {
        g_PhysicSystem.update(dt);
        
        _LimitToScreenSystem();
   },
    
    postDraw:function(ctx){
        
        _DrawAsCircleSystem();
    },
    
    // inputs 
    onTouchesBegan:function (pTouch, event) {
        if( pTouch[0] )
        {
            var localTouch = this.convertTouchToNodeSpace(pTouch[0]);
            var worldTouch = this.convertToWorldSpace(localTouch);
            var b2WorldPos = transformToB2World(worldTouch);
            this.b2MousePosition = b2WorldPos;
        }
    },
    onTouchesMoved:function(pTouch,pEvent){
        if( pTouch[0] )
        {
            var localTouch = this.convertTouchToNodeSpace(pTouch[0]);
            var worldTouch = this.convertToWorldSpace(localTouch);
            worldTouch = cc.Director.getInstance().convertToGL(worldTouch);
            var b2WorldPos = new b2Vec2( worldTouch.x / PTM_RATIO, worldTouch.y / PTM_RATIO );
            
            this.b2MousePosition = b2WorldPos;
        }
    },
    onTouchesEnded:function (pTouch,pEvent){
    },
    onKeyUp:function(e){
    },
    onKeyDown:function(e){
    },
});


var RigidBodyScene = cc.Scene.extend({
    onEnter:function () {
        this._super();
        var layer = new _RigidBodyScene();
        layer.init();
        this.addChild(layer);
    }
});


